
#include <apr_strings.h>
#include "../iface/debug.h"
#include "../iface/APRInterface.h"
#include "../utils/StringUtils.h"
#include "APRFile.h"
#include "APRDOMDocument.h"

#define XML_PARSER_BLOCK_SIZE_APR 4096
/** Initialises the parser for the DOM document instance and sets
 * initial values.
 * 
 * @param imp The parent memory pool from which to create the memory
 * pool for this object. */
apr_xml_parser *APRDOMDocument::initXMLParser(apr_pool_t *imp)
{
   apr_status_t status;
   docpntr = NULL;
   parent = imp;
   status = apr_pool_create(&mp, imp);
   CheckAPRError(status);
   apr_xml_parser *iparser = apr_xml_parser_create(mp);
   return iparser;
}

/** Constructor for the DOM document, reading the document from a
 * buffer.
 * 
 * @param buffer The buffer from which to read the XML document data.
 * 
 * @param length The length of the buffered text which needs to be
 * used to construct the DOM document.
 * 
 * @param imp The parent memory pool from which to create the memory
 * pool for this object. */
APRDOMDocument::APRDOMDocument(const char *buffer, int length, apr_pool_t *imp)
{
   docparser = initXMLParser(imp);
   if (readIn(buffer, length) != 0)
   {
      errprint("could not parse buffer of XML data");
   }
}

/** Constructor for the DOM document, reading the document from a
 * file.
 * 
 * @param filename The name of the file to read.
 * 
 * @param imp The parent memory pool from which to create the memory
 * pool for this object. */
APRDOMDocument::APRDOMDocument(const char *filename, apr_pool_t *imp)
{
   docparser = initXMLParser(imp);
   if (readIn(filename) != 0)
   {
      errprint("could not parse XML file '%s'", filename);
   }
}

/** Constructor for the DOM document which initialises just the
 * parser.
 * 
 * @param imp The parent memory pool from which to create the memory
 * pool for this object. */
APRDOMDocument::APRDOMDocument(apr_pool_t *imp)
{
   docparser = initXMLParser(imp);
}

/** Destructor for the DOM document. */
APRDOMDocument::~APRDOMDocument()
{
   apr_pool_destroy(mp);
}

/** Add a block of data into the parser.
 * 
 * @param data The block of data in a buffer.
 * 
 * @param datalen The length of data to be added to the parser.
 * 
 * @return One if successful, zero on failure. */
int APRDOMDocument::addXMLData(const char *data, int datalen)
{
   if (docparser == NULL)
   {
      errprint("can't add more data: parser complete!");
      return 0;
   }
   apr_status_t status;
   status = apr_xml_parser_feed(docparser, data, datalen);
   CheckAPRError(status);
   return (status == APR_SUCCESS);
}

/** Finalise the XML parsing process.
 * 
 * @return One on success, zero on failure. */
int APRDOMDocument::completeXMLParsing()
{
   apr_status_t status;
   status = apr_xml_parser_done(docparser, &docpntr);
   CheckAPRError(status);
   if (status != APR_SUCCESS) return 0;
   docparser = NULL;
   return 1;
}

/** Get the root node of the XML document tree. \
 * 
 * @return A pointer to the root element. */
apr_xml_elem *APRDOMDocument::getRootNode()
{
   if (docpntr == NULL) return NULL;
   return docpntr->root;
}

/** Generate a root node for a blank document with the given name.
 * 
 * @param name The name of the root node to be generated in this blank
 * document. */
void APRDOMDocument::createRootNode(const char *name)
{
   apr_xml_elem *elem = (apr_xml_elem *)apr_pcalloc(mp, sizeof(apr_xml_elem));
   elem->name = apr_pstrdup(mp, name);
}

/** Gets the attribute node with the given name of the given node.
 * 
 * @param node The node whose attribute value is to be obtained.
 * 
 * @param attribname The name of the attribute to be evaluated.
 * 
 * @return The attribute node pointer. */
apr_xml_attr *APRDOMDocument::getAttribute(apr_xml_elem *node, const char *attribname)
{
   apr_xml_attr *nodeattr = node->attr;
   while (nodeattr != NULL)
   {
      if ((nodeattr->name) && (strcmp(attribname, nodeattr->name) == 0)) return nodeattr;
      nodeattr = nodeattr->next;
   }
   return NULL;
}

/** Gets the value string of the given node with the attribute name.
 * 
 * @param node The node whose attribute value is to be obtained.
 * 
 * @param attribname The name of the attribute to be evaluated.
 * 
 * @return The value of that attribute. */
const char *APRDOMDocument::getAttributeValueString(apr_xml_elem *node, const char *attribname)
{
   apr_xml_attr *nodeattr = getAttribute(node, attribname);
   if (nodeattr != NULL) return nodeattr->value;
   return NULL;
}

/** Sets an attribute of an XML node.
 * 
 * @param elem The node (tag) of the XML tree to be changed.
 * 
 * @param key The key variable name of the attribute to change.
 * 
 * @param value The value of the attribute that needs to be
 * modified. */
void APRDOMDocument::setAttribute(apr_xml_elem *elem, const char *key, const char *value)
{
   apr_xml_attr *attr = (apr_xml_attr *)apr_palloc(mp, sizeof(apr_xml_attr));
   attr->name = apr_pstrdup(mp, key);
   attr->value = apr_pstrdup(mp, value);
   attr->next = elem->attr;
   elem->attr = attr;
}

/** Create a child node (tag) directly inside the current node
 * (between the open and close tags).
 * 
 * @param node The node that this node is to be created inside/as a
 * child of.
 * 
 * @param name The name of the new node/tag pair.
 * 
 * @return A pointer to the new child node. */
apr_xml_elem *APRDOMDocument::createChildNode(apr_xml_elem *node, const char *name)
{
   apr_xml_elem *elem = (apr_xml_elem *)apr_pcalloc(mp, sizeof(apr_xml_elem));
   elem->name = apr_pstrdup(mp, name);
   elem->next = node->first_child;
   elem->parent = node;
   node->first_child = elem;
   return elem;
}

/** Find a node with the given name, attribute and attribute value.
 * 
 * @param node The pointer to the root node to search from. Only
 * subtrees of this node are considered.
 * 
 * @param name Name of the node to find.
 * 
 * @return Returns the node found if any, otherwise NULL if no node is
 * found. */
apr_xml_elem *APRDOMDocument::matchNodeInSubtree(apr_xml_elem *node, const char *name)
{
   if (strcmp(node->name, name) == 0) return node;
   if (node->first_child)
   {
      apr_xml_elem *child_match = matchNodeInSubtree(node->first_child, name);
      if (child_match) return child_match;
   }
   if (node->next)
   {
      apr_xml_elem *next_match = matchNodeInSubtree(node->next, name);
      if (next_match) return next_match;
   }
   return NULL;
}

/** Find a node with the given name, attribute and attribute value.
 * 
 * @param node The pointer to the root node to search from. Only
 * subtrees of this node are considered.
 * 
 * @param name Name of the node to find.
 * 
 * @param attribname Name of the attribute that must exist under a
 * given candidate.
 * 
 * @return Returns the node found if any, otherwise NULL if no node is
 * found. */
apr_xml_elem *APRDOMDocument::matchNodeInSubtree(apr_xml_elem *node, const char *name, const char *attribname)
{
   if (name == NULL) return NULL;
   if (attribname == NULL) return NULL;
   if (strcmp(node->name, name) == 0)
   {
      apr_xml_attr *nodeattr = node->attr;
      while (nodeattr != NULL)
      {
         if ((nodeattr->name) && (strcmp(attribname, nodeattr->name) == 0)) return node;
         nodeattr = nodeattr->next;
      }
   }
   if (node->first_child)
   {
      apr_xml_elem *child_match = matchNodeInSubtree(node->first_child, name, attribname);
      if (child_match) return child_match;
   }
   if (node->next)
   {
      apr_xml_elem *next_match = matchNodeInSubtree(node->next, name, attribname);
      if (next_match) return next_match;
   }
   return NULL;
}

/** Find a node with the given name, attribute and attribute value.
 * 
 * @param node The pointer to the root node to search from. Only
 * subtrees of this node are considered.
 * 
 * @param name Name of the node to find.
 * 
 * @param attribname Name of the attribute that must exist under a
 * given candidate.
 * 
 * @param attribvalue The value of the attribute under the node, which
 * must also match.
 * 
 * @return Returns the node found if any, otherwise NULL if no node is
 * found. */
apr_xml_elem *APRDOMDocument::matchNodeInSubtree(apr_xml_elem *node, const char *name, const char *attribname, const char *attribvalue)
{
   if (name == NULL) return NULL;
   if (attribname == NULL) return NULL;
   if (attribvalue == NULL) return NULL;
   if (strcmp(node->name, name) == 0)
   {
      apr_xml_attr *nodeattr = node->attr;
      while (nodeattr != NULL)
      {
         if ((nodeattr->name) &&
             (nodeattr->value) &&
             (strcmp(attribname, nodeattr->name) == 0) &&
             (strcmp(attribvalue, nodeattr->value) == 0))
            return node;
         nodeattr = nodeattr->next;
      }
   }
   if (node->first_child)
   {
      apr_xml_elem *child_match = matchNodeInSubtree(node->first_child, name, attribname, attribvalue);
      if (child_match) return child_match;
   }
   if (node->next)
   {
      apr_xml_elem *next_match = matchNodeInSubtree(node->next, name, attribname, attribvalue);
      if (next_match) return next_match;
   }
   return NULL;
}

/** Read in a document to store in this instance from a buffer.
 * 
 * @param buffer Buffer holding the XML document text.
 * 
 * @param length Length of the buffered text.
 * 
 * @return Zero on success, non-zero on failure. */
int APRDOMDocument::readIn(const char *buffer, int length)
{
   if (docparser == NULL)
   {
      errprint("class already contains a document!");
      return -1;
   }
   if (!addXMLData(buffer, length)) return 1;
   if (!completeXMLParsing()) return 1;
   docparser = NULL;
   return 0;
}

/** Read in a document to store in this instance from a file.
 * 
 * @param filename The name of the file to open and read the XML
 * document from.
 * 
 * @return Zero on success, non-zero in failure. */
int APRDOMDocument::readIn(const char *filename)
{
   if (docparser == NULL)
   {
      errprint("class already contains a document!");
      return -1;
   }
   apr_status_t status;
   apr_file_t *newFP;
   status = APRFile::openWrapper(&newFP, filename, APR_FOPEN_READ|APR_FOPEN_BUFFERED, APR_FPROT_OS_DEFAULT, mp);
   CheckAPRError(status);
   if (status != APR_SUCCESS) return 1;
   status = apr_xml_parse_file(mp, &docparser, &docpntr, newFP, XML_PARSER_BLOCK_SIZE_APR);
   CheckAPRError(status);
   if (status != APR_SUCCESS) return 1;
   docparser = NULL;
   return 0;
}

/** Writes out the document embedded in this instance to a file.
 * 
 * @param filename The file to write the document to.
 * 
 * @return Zero if successful, non-zero on error. */
int APRDOMDocument::writeOut(const char *filename)
{
   const char *xmltext = getText();
   if (xmltext == NULL)
   {
      errprint("could not get XML text, outputting to file '%s' not possible", filename);
      return 1;
   }
   if (StringUtils::writeStringToFile(xmltext, filename, mp) != 0)
   {
      errprint("writing out XML to '%s' failed", filename);
      return 1;
   }
   return 0;
}

/** Writes out the document embedded in this instance to text
 * allocated from the memory pool.
 * 
 * @return The XML text string. */
const char *APRDOMDocument::getText()
{
   return getTextFromNode(getRootNode());
}

/** Writes out the document subtree given to a text buffer allocated
 * in the memory pool.
 * 
 * @return The string representing the text version. */
const char *APRDOMDocument::getTextFromNode(apr_xml_elem *root)
{
   apr_status_t status;
   apr_size_t textlength = 0;
   const char *textbuf = NULL;
   apr_xml_to_text(mp, root, APR_XML_X2T_FULL, NULL, NULL, &textbuf, &textlength);
   if (textlength > 0)
      return textbuf;
   errprint("could not write XML text data");
   return NULL;
}
